package net.gdface.service.client;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.facebook.swift.service.RuntimeTException;

import net.gdface.cli.BaseAppConfig;
import net.gdface.db.DatabaseManager;
import net.gdface.facedb.CommonConstant;
import net.gdface.facedb.FaceDb;
import net.gdface.facedb.FaceDbGenericDecorator;
import net.gdface.facedb.thrift.FaceDbThriftClient;
import net.gdface.service.search.SaveWorker.FixCountRetryStrategy;
import net.gdface.service.search.SaveWorker.RetryStrategy;
import net.gdface.utils.ConfigUtils;
import net.gdface.utils.Configuration;
import net.gdface.utils.BinaryUtils;
import net.gdface.utils.SimpleTypes;
import net.gdface.utils.IServiceException;
import net.gdface.worker.WorkData;

/**
 * 客户端人脸识别数据库应用程序的基类<br>
 * 提供基本的参数加载,参数初始化,{@link FaceDb}初始化,本地数据库初始化等功能,<br>
 * 应用程序继承该类后,根据自己的需要重载相应的方法来实现自己的功能<br>
 * @author guyadong
 *
 */
public class LocalApp extends BaseAppConfig implements U4fdbConstants,CommonConstant{
	protected static final Logger logger = LoggerFactory.getLogger(LocalApp.class);
	protected static final String APP_NAME = "LOCAL APP";
	protected static final String TARGET_ENDPOINT = "http://#host#:8080/axis2/services/FaceDbService.FaceDbServiceHttpSoap12Endpoint/";
	private static final boolean DEFAULT_RUNDB_INMEMORY=false;
	private static final boolean DEFAULT_RESEARCH=false;
	private static final int DEFAULT_MAX_WORKERTHREADS=Runtime.getRuntime().availableProcessors()*4;
	private static final int DEFAULT_DBBACKUP_INTERVALSECONDS=300;
	private static final int DEFAULT_WORKQUEUE_SIZE=100;
	private static final int DEFAULT_DELAYQUEUESIZE=100;
	private static final int DEFAULT_IMG_MIN_WIDTH=128;
	private static final  int DEFAULT_IMG_MIN_HEIGHT=128;
	private static final int DEFAULT_IMG_MAX_WIDTH=2048;
	private static final int DEFAULT_IMG_MAX_HEIGHT=2048;
	private static final int DEFAULT_ADDIMAGE_FACENUM=0;
	private static final boolean DEFAULT_ADDIMAGE_MUSTMATCH=false;
	private static final boolean DEFAULT_ADDIMAGE_OVERWRITE=false;
///////////////////////配置变量,在loadParametersFromProperties中初始化///////////////////
	protected String host;
	protected int port;
	protected boolean runDbInMemory;
	protected boolean reSearch;
	protected int dbBackupIntervalSeconds;
	protected int workQueueSize;
	protected int delayQueueSize;
	protected int imgMinWidth;
	protected int imgMinHeight;
	protected int imgMaxWidth;
	protected int imgMaxHeight;
	protected int addImageFaceNum ;
	protected boolean addImageMustMatch;
	protected boolean addImageOverwrite;
	//////////////////////////////////////////////////
	protected File root = null;
	protected FaceDbGenericDecorator facedb;
	protected int maxWorkerThreads ;
	protected ExecutorService workerPool;
	/**
	 * 数据库定期备份线程
	 */
	protected Thread dbBackupThread=null;
	/**
	 * 全局结束标志
	 */
	protected AtomicBoolean isFinished = new AtomicBoolean(false);
	protected final List<Thread> threadList = new ArrayList<Thread>();
	/**
	 * 是否使用数据库标记，子类如果不需要用到数据库，就将此变量置为true,则后续类初始化时不会执行与数据库相关的任何操作
	 */
	protected boolean useDatabase=true;
	/**
	 * 自定义重试策略,当{@link Throwable}为timeout异常时,无限重试尝试
	 */
	protected static final RetryStrategy CUSTOM_RETRY_STRATEGY=new FixCountRetryStrategy(1){
		@Override
		public boolean needRetry(WorkData woc, Throwable e) {
			return (SimpleTypes.isNetworkError(e))?super.needRetry(woc, e):false;
		}		
	};
	public static final Configuration CONFIG = getConfig();
	private static Configuration getConfig() {
        String envVar="U4FDB_CONFIG";
        String confFolder="conf";
        String propertiesFile = "u4fdb.properties";
        Properties props = ConfigUtils.loadAllProperties(propertiesFile, confFolder, envVar, LocalApp.class, false);
        Properties userProps = ConfigUtils.loadPropertiesInUserHome(props, ".u4fdb/" + propertiesFile);
        return new Configuration(userProps);
	}
	public LocalApp() {
		loadParametersFromProperties();
		
		options.addOption(Option.builder().longOpt(FACEDB_HOST_OPTION_LONG)
				.desc(FACEDB_HOST_OPTION_DESC).numberOfArgs(1).build());
		options.addOption(Option.builder().longOpt(FACEDB_PORT_OPTION_LONG)
				.desc(FACEDB_PORT_OPTION_DESC + DEFAULT_PORT).numberOfArgs(1).type(Number.class).build());
		options.addOption(Option.builder(QUEUE_SIZE_OPTION).longOpt(QUEUE_SIZE_OPTION_LONG)
				.desc(QUEUE_SIZE_OPTION_DESC + DEFAULT_WORKQUEUE_SIZE).numberOfArgs(1).type(Number.class).build());
		options.addOption(Option.builder(MAX_THREAD_OPTION).longOpt(MAX_THREAD_OPTION_LONG)
				.desc(MAX_THREAD_OPTION_DESC + DEFAULT_MAX_WORKERTHREADS).numberOfArgs(1).type(Number.class).build());
		options.addOption(Option.builder(WORK_FOLDER_OPTION).longOpt(WORK_FOLDER_OPTION_LONG)
				.desc(WORK_FOLDER_OPTION_DESC).numberOfArgs(1).type(File.class).build());
		options.addOption(Option.builder(RESEARCH_OPTION).longOpt(RESEARCH_OPTION_LONG)
				.desc(RESEARCH_OPTION_DESC).build());
		options.addOption(Option.builder(FACE_NUM_OPTION).longOpt(FACE_NUM_OPTION_LONG)
				.desc(FACE_NUM_OPTION_DESC).numberOfArgs(1).type(Number.class).build());
		options.addOption(Option.builder(MUST_MATCH_OPTION).longOpt(MUST_MATCH_OPTION_LONG)
				.desc(MUST_MATCH_OPTION_DESC).build());
		options.addOption(Option.builder(OVERWRITE_OPTION).longOpt(OVERWRITE_OPTION_LONG)
				.desc(OVERWRITE_OPTION_DESC).build());
		options.addOption(Option.builder().longOpt(DERBY_IN_MEMORY_OPTION_LONG)
				.desc(DERBY_IN_MEMORY_OPTION_DESC).build());

	}
	
	@Override
	protected Map<String, Object> getDefaultValueMap() {
		
		defaultValue.setProperty(FACEDB_HOST_OPTION_LONG,host);
		defaultValue.setProperty(FACEDB_PORT_OPTION_LONG,port);
		defaultValue.setProperty(QUEUE_SIZE_OPTION_LONG,workQueueSize);
		defaultValue.setProperty(MAX_THREAD_OPTION_LONG,maxWorkerThreads);
		defaultValue.setProperty(WORK_FOLDER_OPTION_LONG,root);
		defaultValue.setProperty(FACE_NUM_OPTION_LONG,addImageFaceNum);
		
		return super.getDefaultValueMap();
	}

	@Override
	public void loadConfig(Options options, CommandLine cmd) throws ParseException {
		super.loadConfig(options, cmd);
		host  = getProperty(FACEDB_HOST_OPTION_LONG);
		port  =  ((Number)getProperty(FACEDB_PORT_OPTION_LONG)).intValue();
		workQueueSize  = ((Number)getProperty(QUEUE_SIZE_OPTION_LONG)).intValue();
		maxWorkerThreads  = ((Number)getProperty(MAX_THREAD_OPTION_LONG)).intValue();
		root  = getProperty(WORK_FOLDER_OPTION_LONG);
		try {
			root = root.getCanonicalFile();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		if(cmd.hasOption(RESEARCH_OPTION_LONG)){
			reSearch  = true;
		}
		addImageFaceNum  = ((Number)getProperty(FACE_NUM_OPTION_LONG)).intValue();
		if(cmd.hasOption(MUST_MATCH_OPTION_LONG)){
			addImageMustMatch  = true;
		}
		if(cmd.hasOption(OVERWRITE_OPTION_LONG)){
			addImageOverwrite  = true;
		}
		if(cmd.hasOption(DERBY_IN_MEMORY_OPTION_LONG)){
			runDbInMemory  = true;
		}
	}
	/**
	 * 创建数据库实例<br>
	 * 用到数据库的应用必须重写此方法
	 * @throws Exception TODO
	 */
	protected void createDbInstance() throws Exception{		
	}
	/**
	 * 初始化类
	 * @throws Exception 
	 */
	protected void init() throws Exception {
		if (!(this.root.exists() && this.root.isDirectory())){
			throw new FileNotFoundException();
		}			
		workerPool = Executors.newFixedThreadPool(this.maxWorkerThreads);
		
		if(useDatabase){
			createDbInstance();
		}
			
		logger.info("Thread workerPool size(线程池大小)=[{}]", this.maxWorkerThreads);
			
		FaceDbThriftClient thriftInstance = new FaceDbThriftClient(host,port);
		facedb= new FaceDbGenericDecorator(thriftInstance);
	
	}

	/**
	 * 从配置文件中加载参数<br>
	 * 子类重写时必须调用父类方法
	 */
	protected void loadParametersFromProperties() {
		CONFIG.setPrefix(LocalApp.class.getName() + ".");
		this.host = CONFIG.getPropertyBaseType("host", DEFAULT_FACEDB_HOST);
		this.port = CONFIG.getPropertyBaseType("port", DEFAULT_PORT);
		this.runDbInMemory = CONFIG.getPropertyBaseType("runDbInMemory", DEFAULT_RUNDB_INMEMORY);
		this.reSearch = CONFIG.getPropertyBaseType("reSearch", DEFAULT_RESEARCH);		
		this.maxWorkerThreads = CONFIG.getPropertyBaseType("maxWorkerThreads", DEFAULT_MAX_WORKERTHREADS);
		this.dbBackupIntervalSeconds = CONFIG.getPropertyBaseType("dbBackupIntervalSeconds", DEFAULT_DBBACKUP_INTERVALSECONDS);
		this.workQueueSize = CONFIG.getPropertyBaseType("workQueueSize", DEFAULT_WORKQUEUE_SIZE);
		this.delayQueueSize = CONFIG.getPropertyBaseType("delayQueueSize", DEFAULT_DELAYQUEUESIZE);
		this.imgMinWidth = CONFIG.getPropertyBaseType("imgMinWidth", DEFAULT_IMG_MIN_WIDTH);
		this.imgMinHeight = CONFIG.getPropertyBaseType("imgMinHeight", DEFAULT_IMG_MIN_HEIGHT);
		this.imgMaxWidth = CONFIG.getPropertyBaseType("imgMaxWidth", DEFAULT_IMG_MAX_WIDTH);
		this.imgMaxHeight = CONFIG.getPropertyBaseType("imgMaxHeight", DEFAULT_IMG_MAX_HEIGHT);
		this.addImageFaceNum = CONFIG.getPropertyBaseType("addImageFaceNum", DEFAULT_ADDIMAGE_FACENUM);
		this.addImageMustMatch = CONFIG.getPropertyBaseType("addImageMustMatch", DEFAULT_ADDIMAGE_MUSTMATCH);
		this.addImageOverwrite = CONFIG.getPropertyBaseType("addImageOverwrite", DEFAULT_ADDIMAGE_OVERWRITE);
		this.root = new File(CONFIG.getPropertyBaseType("workFolder", "."));		
	}
	
	@Override
	protected String getAppName() {
		return APP_NAME;
	}
	/**
	 * 程序被用户中断时的处理
	 * 子类在覆盖该方法时，必须在方法结尾调用父类的方法 
	 */
	protected void onFinished() {
		if(this.useDatabase){
			DatabaseManager.getInstance().onFinished();
		}
	}
	/**
	 *  子类在覆盖该方法时，必须在方法结尾调用父类的方法 
	 * @throws Exception 
	 */
	protected final void start() throws Exception {
		init();
		// 加入系统hook，当按程序正常结束或Ctrl-C退出时保存未完成的工作，以便下次继续
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				logger.info("结束线程开始");
				isFinished.set(true);
				onFinished();
				logger.info("结束线程结束");
			}
		});
		if(useDatabase){
			if(runDbInMemory){
				this.dbBackupThread = DatabaseManager.getInstance().startDbBackuper();
				// 加入线程列表，异常中止时由ShutdownHook负责中断
				this.threadList.add(this.dbBackupThread);
			}
		}
		try {
			startApp();
		} catch (InterruptedException e) {
			logger.info("User Break...(运行中断).");
			Thread.currentThread().interrupt();
		}finally{
			this.isFinished.set(true);// 设置全局结束标志
			//中止所有注册线程
			logger.info("Interrupt all thread registered...中止注册线程).");
			for (Thread t : threadList) {
				if (t.isAlive()) {
					t.interrupt();
					logger.info("中断线程interrupt thread[{}] ", t.getName());
				}
			}
			//正常结束状态下中止并等待所有工作线程结束
			//子类应该保证自己启动的线程都结束，这里只是做最后的处理
			shutdownAndAwaitTermination(this.workerPool);
			logger.info("all worker threads finished.所有工作线程结束");
			logger.info("主线程结束");			
		}

	}
	/**
	 *子类重写该方法
	 * @throws InterruptedException 
	 */
	protected  void startApp() throws InterruptedException{} 


	protected void shutdownAndAwaitTermination(ExecutorService pool) {
		if(null==pool)
			return;
		pool.shutdown(); // Disable new tasks from being submitted
		try {
			// Wait a while for existing tasks to terminate
			if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
				pool.shutdownNow(); // Cancel currently executing tasks
				// Wait a while for tasks to respond to being cancelled
				if (!pool.awaitTermination(60, TimeUnit.SECONDS))
					// System.err.println("Pool did not terminate");
					logger.error("Pool did not terminate");
			}
		} catch (InterruptedException ie) {
			// (Re-)Cancel if current thread also interrupted
			pool.shutdownNow();
			// Preserve interrupt status
			Thread.currentThread().interrupt();
		}
	}
	
	/**
	 * 拦截输出所有webservice调用异常,抛出其他异常
	 * @param e
	 * @param logger
	 * @throws RuntimeException 
	 */
	public static final void filterWebserviceFault(RuntimeException e,Logger logger) throws RuntimeException{
		Throwable fault = SimpleTypes.stripThrowableShell(e, RuntimeException.class);
		if(fault instanceof IServiceException){
			logger.error(((IServiceException)e).getServiceStackTraceMessage());
		}else if( fault instanceof RuntimeTException || fault instanceof TException){
			logger.error(fault.toString());
		}else if( SimpleTypes.isNetworkError(fault)){
			logger.error(fault.toString());
		}else{
			throw e;
		}
	}
}